iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0
自我挑戰組

LDD3 (Linux Device Drivers, 3th) 學習筆記系列 第 4

[Day04] Chapter 3: Char Drivers (1)

  • 分享至 

  • xImage
  •  

Summarize

  • Char devices are accessed through names in the filesystem. Those names are called
    special files or device files or simply nodes of the filesystem tree
  • We can consider the file to be an “object” and the functions operating on it to be its “methods,” using object-oriented programming terminology to denote actions declared by an object to act on itself
  • As you read through the list of file_operations methods, you will note that a number of parameters include the string "__user". This annotation is a form of documentation, noting that a pointer is a user-space address that cannot be directly dereferenced

Outline

  • Major and Minor Numbers
  • The Internal Representation of Device Numbers
  • Allocating and Freeing Device Numbers
  • File Operations

註: 筆者覺得這章節用的 scull (Simple Character Utility for Loading Localities) 有點過時了,上網找到類似,但更喜歡的範例分享

Major and Minor Numbers

  • Char devices are accessed through names in the filesystem. Those names are called
    special files or device files or simply nodes of the filesystem tree;
    • they are conventionally located in the /dev directory

在列出 /dev/ 目錄時,你會看到每個設備文件的條目中有兩個用逗號分隔的數字(通常在文件最後修改日期前面顯示),這兩個數字就是設備的 主設備號(major number) 和 次設備號(minor number)

brw-rw----.  1 root     disk    259,   0 Sep  6 13:10 nvme0n1
brw-rw----.  1 root     disk    259,   1 Sep  6 13:10 nvme0n1p1
lrwxrwxrwx.  1 root     root          15 Sep  6 13:10 stderr -> /proc/self/fd/2
lrwxrwxrwx.  1 root     root          15 Sep  6 13:10 stdin -> /proc/self/fd/0
lrwxrwxrwx.  1 root     root          15 Sep  6 13:10 stdout -> /proc/self/fd/1
crw-rw-rw-.  1 root     tty       5,   0 Sep  6 13:10 tty
crw--w----.  1 root     tty       4,   0 Sep  6 13:10 tty0
crw--w----.  1 root     tty       4,   1 Sep  6 13:10 tty1
  • 主設備號(major number):標識負責管理該設備的驅動程式。例如,主設備號 259 對應 NVMe 驅動,而 4 對應虛擬終端驅動
  • 次設備號(minor number):用於區分由同一驅動程式管理的不同設備或設備實例。例如,主設備號 4 的不同次設備號(如 0 和 1)區分了 tty0 和 tty1 這兩個不同的虛擬終端

The Internal Representation of Device Numbers

  • the dev_t type (defined in <linux/types.h>) is used to hold device numbers—both the major and minor parts
  • never make any assumptions about the internal organization of device numbers; it should, instead, make use of a set of macros found in <linux/kdev_t.h>
  • To obtain the major or minor parts of a dev_t, use:
    • MAJOR(dev_t dev);
    • MINOR(dev_t dev);
  • If, instead, you have the major and minor numbers and need to turn them into a dev_t, use:
    • MKDEV(int major, int minor);

Allocating and Freeing Device Numbers

在撰寫 Linux 字符設備驅動程式時,設備號碼的分配與釋放是第一步需要處理的工作。Linux 為驅動程式提供了兩種分配設備號的方式:靜態分配和動態分配

  1. 靜態分配設備號:register_chrdev_region
  • 這個函數允許你指定從哪個主設備號和次設備號開始,以及需要多少個連續的設備號
int register_chrdev_region(dev_t first, unsigned int count, char *name);
參數說明:
first: 你想分配的第一個設備號(包括主設備號和次設備號),通常次設備號設為 0。
count: 你希望分配的設備號總數。
name: 設備名稱,該名稱將出現在 /proc/devices 和 sysfs 中。
回傳值:
成功時返回 0,如果失敗,返回負數錯誤碼。

範例:

dev_t dev;
int result = register_chrdev_region(MKDEV(240, 0), 1, "mychardev");
if (result < 0) {
    printk(KERN_WARNING "Can't allocate device number\n");
    return result;
}
  • 在這個範例中,主設備號設為 240,次設備號設為 0,並且分配了一個設備號
  1. 動態分配設備號:alloc_chrdev_region
  • 如果你不知道需要分配的主設備號,Linux 內核提供了一個動態分配設備號的機制,允許你請求內核自動分配主設備號
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
參數說明:
dev: 指向 dev_t 的指標,當分配成功後,內核將分配的主設備號和次設備號儲存在此處
firstminor: 要求分配的第一個次設備號,通常設為 0
count: 要分配的設備數量
name: 設備名稱,將顯示在 /proc/devices 和 sysfs 中
回傳值:
成功時返回 0,如果失敗,返回負數錯誤碼

範例:

dev_t dev;
int result = alloc_chrdev_region(&dev, 0, 1, "mychardev");
if (result < 0) {
    printk(KERN_WARNING "Can't allocate device number\n");
    return result;
}
printk(KERN_INFO "Allocated device number: major=%d, minor=%d\n", MAJOR(dev), MINOR(dev));

在這個範例中,內核會動態分配一個主設備號,次設備號設為 0,並分配 1 個設備號

  1. 釋放設備號:unregister_chrdev_region
  • 無論使用靜態還是動態分配設備號,你都應該在驅動程式卸載或設備不再使用時,釋放這些設備號
void unregister_chrdev_region(dev_t first, unsigned int count);
參數說明:
first: 要釋放的起始設備號
count: 要釋放的設備號的數量

範例:

unregister_chrdev_region(dev, 1);

在這個範例中,釋放了一個先前分配的設備號。

  1. 設備號的分配與驅動操作的關聯
  • 分配設備號後,這些設備號還未與實際的設備操作綁定,僅僅是分配好了編號
  • 要使設備號可以被應用程式使用,必須將它們與驅動程序的操作(如打開、讀寫等)連接起來
  • 這通常通過 cdev 結構來完成。具體如何完成這種連接,將在進一步的驅動開發過程中介紹

File Operations

We can consider the file to be an “object” and the functions operating on it to be its “methods,” using object-oriented programming terminology to denote actions declared by an object to act on itself

The file_operations structure is how a char driver sets up this connection.

  • The structure, defined in <linux/fs.h>, is a collection of function pointers
  • Each open file is associated with its own set of functions (by including a field called f_op that points to a file_operations structure)

file_operations 結構體的定義

  • file_operations 結構體定義在 linux/fs.h 中,它包含多個函數指標,每個指標對應於一個具體的操作,例如打開文件、讀取數據、寫入數據等
  • 這些操作是由驅動程式提供的函數來實現,並在應用程式對設備進行操作(如 read()、write()、open() 等系統調用)時被觸發
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

NOTE: 未顯式賦值的結構成員將默認為 NULL

file_operations 結構體的實例通常命名為 fops,這是 Linux 字符設備驅動中的慣例

  • 當你在驅動程式中實現具體的操作函數後,需要將這些函數的地址分配給 file_operations 結構,讓內核知道如何處理對應的系統調用
  • 例如,實現一個簡單的字符設備驅動程式,fops 可以這樣初始化:
struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

As you read through the list of file_operations methods, you will note that a number of parameters include the string "__user"

  • This annotation is a form of documentation, noting that a pointer is a user-space address that cannot be directly dereferenced
  • 補充:如何處理用戶空間地址?
    • 當字符設備驅動處理來自用戶空間的數據時,內核無法直接解引用 __user 標記的指標。取而代之,內核必須通過如下的 API 來安全地訪問用戶空間:
      • copy_from_user(void *to, const void __user *from, unsigned long n)
      • copy_to_user(void __user *to, const void *from, unsigned long n)
    • 這些 API 保證內核不會因為無效的用戶空間地址而崩潰,並且進行了相應的邊界檢查來確保內存訪問的安全性

小結

<linux/fs.h> 定義了文件操作和文件系統交互的核心結構,如 file_operations、inode、file 等。這些結構通過函數指標將具體的設備操作與系統調用連接起來:

  • file_operations 結構體 是字符設備驅動的核心,驅動程式通過該結構體將內核的文件操作與具體的設備功能綁定
  • inode 結構體 是文件系統中的基本單位,負責管理文件的元數據和設備操作
  • VFS 提供了一個統一的接口,通過這些結構體抽象了不同的文件系統,確保設備驅動和文件系統之間的靈活交互

Reference

  1. 4.1. Character Device Drivers
  2. Driver (驅動)

上一篇
[Day03] Chapter 2: Building and Running Modules (2)
下一篇
[Day05] Chapter 3: Char Drivers (2)
系列文
LDD3 (Linux Device Drivers, 3th) 學習筆記5
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言